// *** LICENSE HEADER ***
// Filename: ./node.cpp
// This file is part of Nodewarz 0.1
// Nodewarz is a 3d OpenGL strategy game
// Copyright (C) 2007 CZestmyr
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *** END LICENSE HEADER ***
#include "node.h"

node::node(node* newNext, nodeGraph* newPar, float newX, float newY) {
    next = newNext;
    parent = newPar;
    x = newX;
    y = newY;
    thetaEnergy = 10;
    connections = 0;
    selected = false;
    attack = defence = takeover = 20;

    //Set the node color and add a light to the light manager
    SDL_Color color = parent->GetParent()->GetColor();
    if (parent && parent->GetLightManager())
        myLight = parent->GetLightManager()->AddLight(color.r / 256.0f, color.g / 256.0f, color.b / 256.0f, x, y, 2.5f);
    else myLight = 0;
}

node::~node() {
    //Delete the connections
    connNode* it = connections;
    connNode* helper;
    while (it != 0) {
        helper = it;
        it = it->next;
        delete helper;
    }
    //Remove the light from the light manager
    if (myLight && parent && parent->GetLightManager())
        parent->GetLightManager()->RemoveLight(myLight);
}

void node::CopyAtributesFrom(node* other){
    thetaEnergy = other->thetaEnergy;
    attack = other->attack;
    defence = other->defence;
    takeover = other->takeover;
}

void node::Connect(node* B, bool visible) {
    //Do not connect the node to itself
    if (B == this) return;
    //Add the connection
    connections = new connNode(B, connections, visible);
}

bool node::IsConnectedTo(node* B){
    //Search the connection linked-list for the given connection
    connNode* it = connections;
    while (it != 0){
        if (it->connection == B) return true;
        it = it->next;
    }
    return false;
}

void node::Disconnect(node* B) {
    //Delete the given node from the list of connections
    connNode* helper;
    if (B == 0 || connections == 0) return;
    if (B == connections->connection) {
        helper = connections;
        connections = connections->next;
        delete helper;
        return;
    }
    connNode* it = connections;
    while (it->next != 0 && it->next->connection != B)
        it = it->next;
    if (it->next != 0) {
        helper = it->next;
        it->next = it->next->next;
        delete helper;
    }
}

void node::DisconnectAll(){
    //Delete the whole connection list
    connNode* it = connections;
    connNode* helper;
    while (it != 0) {
        helper = it;
        it->connection->Disconnect(this);
        it = it->next;
        delete helper;
    }
    connections = 0;
}

bool node::IsFriendly(node* other){
    if (other->parent == parent) return true;
    else return false;
}

void node::Grow() {
    //For each connection, receive one health point
    connNode* it = connections;
    while (it != 0){
        if (thetaEnergy < 40)
            thetaEnergy++;
        it = it->next;
    }
}

bool node::Damage(int damageDealt) {
    //The defence energy absorbs some of the damage.
    //100% defence absorbs all the damage
    int defenceTaken = defence*damageDealt/100;
    if (defenceTaken > defence) defenceTaken = defence;
    defence -= defenceTaken;
    //Rest of the damage takes node health
    damageDealt -= defenceTaken;
    thetaEnergy -= damageDealt;
    if (thetaEnergy <= 0) {
        DisconnectAll();
        parent->RemoveNode(this);
        return true;
    }
    return false;
}

void node::ImproveAttack() {
    attack += 1;
    defence -= 1;
    takeover -= 1;
    if (attack > 100)
        attack = 100;
    if (defence < 0)
        defence = 0;
    if (takeover < 0)
        takeover = 0;
}

void node::Defend(int healthReceived){
    thetaEnergy += healthReceived / 5;
    if (thetaEnergy > 40)
        thetaEnergy = 40;
    defence += 5;
    if (defence > 100)
        defence = 100;
}

void node::ImproveDefence(){
    defence += 1;
    attack -= 1;
    takeover -= 1;
    if (defence > 100)
        defence = 100;
    if (attack < 0)
        attack = 0;
    if (takeover < 0)
        takeover = 0;
}

void node::FriendlyTakeover(int psiReceived){
    takeover += psiReceived / 5;
    if (takeover > 100)
        takeover = 100;
}

bool node::EnemyTakeover(int psiReceived){
    takeover -= psiReceived / 5;
    if (takeover < 0)
        return true;
    else return false;
}

void node::ImproveTakeover(){
    takeover += 1;
    attack -= 1;
    defence -= 1;
    if (takeover > 100)
        takeover = 100;
    if (attack < 0)
        attack = 0;
    if (defence < 0)
        defence = 0;
}

float node::CalculateProbabilityOfAttack(node* other){
    return CalculateProbabilityOfAttack(other->GetX(), other->GetY());
}

float node::CalculateProbabilityOfAttack(float pX, float pY){
    //Prevent division by zero
    if (attack == 0) return 0.0f;

    float perfectRadius = attack / 20.0f;
    float distance = CalculateDistanceTo(pX, pY);
    if (distance <= perfectRadius) return 0.9f;
    float prob = -6.0f*distance/attack + 1.2f; //Constants here are calculated from limit radius being 4*perfectRadius
    return ((prob>0.0f) ? prob : 0.0f);
}

float node::CalculateProbabilityOfDefence(node* other){
    return CalculateProbabilityOfDefence(other->GetX(), other->GetY());
}

float node::CalculateProbabilityOfDefence(float pX, float pY){
    //Prevent division by zero
    if (defence == 0) return 0.0f;

    float perfectRadius = defence / 20.0f;
    float distance = CalculateDistanceTo(pX, pY);
    if (distance <= perfectRadius) return 0.9f;
    float prob = -6.0f*distance/defence + 1.2f; //Constants here are calculated from limit radius being 4*perfectRadius
    return ((prob>0.0f) ? prob : 0.0f);
}

float node::CalculateProbabilityOfTakeover(node* other){
    return CalculateProbabilityOfTakeover(other->GetX(), other->GetY());
}

float node::CalculateProbabilityOfTakeover(float pX, float pY){
    //Prevent division by zero
    if (takeover == 0) return 0.0f;

    float perfectRadius = takeover / 20.0f;
    float distance = CalculateDistanceTo(pX, pY);
    if (distance <= perfectRadius) return 0.9f;
    float prob = -6.0f*distance/takeover + 1.2f; //Constants here are calculated from limit radius being 4*perfectRadius
    return ((prob>0.0f) ? prob : 0.0f);
}

float node::CalculateDistanceTo(float pX, float pY) {
    return sqrt((pX -x)*(pX - x) + (pY - y)*(pY - y));
}

float node::CalculateDistanceTo(node* another) {
    if (!another) return FLT_MAX;
    return sqrt((another->x - x)*(another->x - x) + (another->y - y)*(another->y - y));
}

void node::ResetLight(){
    //Delete the light and set it anew
    if (parent && parent->GetLightManager()){
        if (myLight)
            parent->GetLightManager()->RemoveLight(myLight);
        SDL_Color color = parent->GetParent()->GetColor();
        myLight = parent->GetLightManager()->AddLight(color.r / 256.0f, color.g / 256.0f, color.b / 256.0f, x, y, 1.5f);
    }
}

void node::Draw(bool showEnergies) {
    GLfloat modelview[16];
    GLfloat color[4];
    GLfloat billX, billY, billZ;

    //Calculate the node dimensions
    float one = thetaEnergy / 160.0f + 0.125f;
    float halve = one / 2.0f;


    //Draw the node
    glBegin(GL_TRIANGLES);
    glVertex3f(x      , y      , one);
    glVertex3f(x - halve, y - halve, 0.0f);
    glVertex3f(x      , y + halve, 0.0f);

    glVertex3f(x      , y      , one);
    glVertex3f(x + halve, y - halve, 0.0f);
    glVertex3f(x - halve, y - halve, 0.0f);

    glVertex3f(x      , y      , one);
    glVertex3f(x      , y + halve, 0.0f);
    glVertex3f(x + halve, y - halve, 0.0f);
    glEnd();

    //Save the current color
    glGetFloatv(GL_CURRENT_COLOR, color);

    //Draw aura around selected nodes
    if (selected) {
        halve *= 1.5f;
        one *= 1.5f;

        glDepthMask(GL_TRUE);
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glColor3f(color[0]/3.0f, color[1]/3.0f, color[2]/3.0f);

        glBegin(GL_TRIANGLES);
        glVertex3f(x      , y      , one);
        glVertex3f(x - halve, y - halve, 0.0f);
        glVertex3f(x      , y + halve, 0.0f);

        glVertex3f(x      , y      , one);
        glVertex3f(x + halve, y - halve, 0.0f);
        glVertex3f(x - halve, y - halve, 0.0f);

        glVertex3f(x      , y      , one);
        glVertex3f(x      , y + halve, 0.0f);
        glVertex3f(x + halve, y - halve, 0.0f);
        glEnd();

        glDisable(GL_BLEND);
        glDepthMask(GL_FALSE);
    }

    if (showEnergies) {

    //Save the current transformation matrix
    glPushMatrix();

    //Calculate the coordinate translation
    glGetFloatv(GL_MODELVIEW_MATRIX, modelview);
    billX = x * modelview[0] + y * modelview[4] + modelview[12];
    billY = x * modelview[1] + y * modelview[5] + modelview[13];
    billZ = x * modelview[2] + y * modelview[6] + modelview[14];

    //Set the transformation matrix to the translation part only
    glLoadIdentity();
    glTranslatef(billX + halve, billY + one, billZ + 0.3f);

    //Draw the energybar background
    if (selected) {
        glColor3f(0.0f, 0.0f, 0.0f);
        glBegin(GL_QUADS);
        glVertex3f(1.0f, 0.4f, 0.0f);
        glVertex3f(1.0f, 0.3f, 0.0f);
        glVertex3f(0.0f, 0.3f, 0.0f);
        glVertex3f(0.0f, 0.4f, 0.0f);
        glEnd();
        glBegin(GL_QUADS);
        glVertex3f(1.0f, 0.3f, 0.0f);
        glVertex3f(1.0f, 0.2f, 0.0f);
        glVertex3f(0.0f, 0.2f, 0.0f);
        glVertex3f(0.0f, 0.3f, 0.0f);
        glEnd();
        glBegin(GL_QUADS);
        glVertex3f(1.0f, 0.2f, 0.0f);
        glVertex3f(1.0f, 0.1f, 0.0f);
        glVertex3f(0.0f, 0.1f, 0.0f);
        glVertex3f(0.0f, 0.2f, 0.0f);
        glEnd();
        glBegin(GL_QUADS);
        glVertex3f(1.0f, 0.1f, 0.0f);
        glVertex3f(1.0f, 0.0f, 0.0f);
        glVertex3f(0.0f, 0.0f, 0.0f);
        glVertex3f(0.0f, 0.1f, 0.0f);
        glEnd();
    }

    //Draw the actual ammounts of energies
    glColor4fv(color);
    glBegin(GL_QUADS);
    glVertex3f(thetaEnergy / 40.0f, 0.4f, 0.0f);
    glVertex3f(thetaEnergy / 40.0f, 0.3f, 0.0f);
    glVertex3f(0.0f, 0.3f, 0.0f);
    glVertex3f(0.0f, 0.4f, 0.0f);
    glEnd();
    glColor3f(1.0f, 1.0f - attack / 100.0f, 1.0f - attack / 100.0f);
    glBegin(GL_QUADS);
    glVertex3f(attack / 100.0f, 0.3f, 0.0f);
    glVertex3f(attack / 100.0f, 0.2f, 0.0f);
    glVertex3f(0.0f, 0.2f, 0.0f);
    glVertex3f(0.0f, 0.3f, 0.0f);
    glEnd();
    glColor3f(1.0f - defence / 100.0f, 1.0f, 1.0f - defence / 100.0f);
    glBegin(GL_QUADS);
    glVertex3f(defence / 100.0f, 0.2f, 0.0f);
    glVertex3f(defence / 100.0f, 0.1f, 0.0f);
    glVertex3f(0.0f, 0.1f, 0.0f);
    glVertex3f(0.0f, 0.2f, 0.0f);
    glEnd();
    glColor3f(1.0f - takeover / 100.0f, 1.0f - takeover / 100.0f, 1.0f);
    glBegin(GL_QUADS);
    glVertex3f(takeover / 100.0f, 0.1f, 0.0f);
    glVertex3f(takeover / 100.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.1f, 0.0f);
    glEnd();

    //Draw frames around the energybars
    if (selected) {
        glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_LINE_STRIP);
        glVertex3f(1.0f, 0.4f, 0.0f);
        glVertex3f(1.0f, 0.3f, 0.0f);
        glVertex3f(0.0f, 0.3f, 0.0f);
        glVertex3f(0.0f, 0.4f, 0.0f);
        glVertex3f(1.0f, 0.4f, 0.0f);
        glEnd();

        glColor3f(1.0f, 0.0f, 0.0f);
        glBegin(GL_LINE_STRIP);
        glVertex3f(1.0f, 0.3f, 0.0f);
        glVertex3f(1.0f, 0.2f, 0.0f);
        glVertex3f(0.0f, 0.2f, 0.0f);
        glVertex3f(0.0f, 0.3f, 0.0f);
        glVertex3f(1.0f, 0.3f, 0.0f);
        glEnd();

        glColor3f(0.0f, 1.0f, 0.0f);
        glBegin(GL_LINE_STRIP);
        glVertex3f(1.0f, 0.2f, 0.0f);
        glVertex3f(1.0f, 0.1f, 0.0f);
        glVertex3f(0.0f, 0.1f, 0.0f);
        glVertex3f(0.0f, 0.2f, 0.0f);
        glVertex3f(1.0f, 0.2f, 0.0f);
        glEnd();

        glColor3f(0.0f, 0.0f, 1.0f);
        glBegin(GL_LINE_STRIP);
        glVertex3f(1.0f, 0.1f, 0.0f);
        glVertex3f(1.0f, 0.0f, 0.0f);
        glVertex3f(0.0f, 0.0f, 0.0f);
        glVertex3f(0.0f, 0.1f, 0.0f);
        glVertex3f(1.0f, 0.1f, 0.0f);
        glEnd();
    }

    //Restore the current color
    glColor4fv(color);

    //Restore the transformation matrix
    glPopMatrix();

    } //End if (showEnergies)

    //Draw the connections for this node
    connNode* it = connections;
    while (it != 0) {
        if (it->drawn) {
            glBegin(GL_LINES);
            glVertex3f(x, y, 0.1f);
            glVertex3f(it->connection->x, it->connection->y, 0.1f);
            glEnd();
        }
        it = it->next;
    }
}

void node::DrawBoundary(bool friendly){
    //Only draw the poly. Textures are set lower in the call stack
    float radius;
    if (friendly)
        radius = _MINDIST_FRIENDLY;
    else
        radius = _MINDIST_ENEMY;

    glBegin(GL_QUADS);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(x + radius, y + radius, 0.012f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(x + radius, y - radius, 0.012f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(x - radius, y - radius, 0.012f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(x - radius, y + radius, 0.012f);
    glEnd();
}

float node::GetX() {
    return x;
}

float node::GetY() {
    return y;
}

int node::GetThetaEnergy(){
    return thetaEnergy;
}

int node::GetAttackEnergy() {
    return attack;
}

int node::GetDefenceEnergy(){
    return defence;
}

int node::GetTakeoverEnergy(){
    return takeover;
}

void node::SetTakeoverEnergy(int newTakeover){
    takeover = newTakeover;
    Constrainti(takeover, 0, 100);
}

void node::Deselect() {
    selected = false;
}

void node::Select() {
    selected = true;
}
